package cn.ityao.es.service.impl;

/**
 * @author: tongyao
 * @since: 2025-07-31 09:25
 */

import cn.ityao.es.entity.ASInfo;
import cn.ityao.es.entity.CountryInfo;
import cn.ityao.es.entity.GeoCNResponse;
import cn.ityao.es.entity.IPInfo;
import cn.ityao.es.service.GeoIPService;
import com.maxmind.db.Reader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.InetAddress;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class GeoIPServiceImpl implements GeoIPService {

    private final Reader cityReader;
    private final Reader asnReader;
    private final Reader cnReader;
    private final List<String> lang = Arrays.asList("zh-CN", "en");
    private final List<String> provinces = Arrays.asList(
            "内蒙古", "黑龙江", "河北", "山西", "吉林", "辽宁", "江苏", "浙江",
            "安徽", "福建", "江西", "山东", "河南", "湖北", "湖南", "广东",
            "海南", "四川", "贵州", "云南", "陕西", "甘肃", "青海", "广西",
            "西藏", "宁夏", "新疆", "北京", "天津", "上海", "重庆"
    );

    private static final Map<Integer, String> ASN_MAP = new HashMap<Integer, String>() {{
        // 有线电视/网络服务商
        put(9812, "东方有线");
        put(9389, "中国长城");
        put(17962, "天威视讯");
        put(17429, "歌华有线");
        put(7497, "科技网");
        put(24139, "华数");
        put(9801, "中关村");
        put(4538, "教育网");
        put(24151, "CNNIC");

        // 中国移动
        put(38019, "中国移动");
        put(139080, "中国移动");
        put(9808, "中国移动");
        put(24400, "中国移动");
        put(134810, "中国移动");
        put(24547, "中国移动");
        put(56040, "中国移动");
        put(56041, "中国移动");
        put(56042, "中国移动");
        put(56044, "中国移动");
        put(132525, "中国移动");
        put(56046, "中国移动");
        put(56047, "中国移动");
        put(56048, "中国移动");
        put(59257, "中国移动");
        put(24444, "中国移动");
        put(24445, "中国移动");
        put(137872, "中国移动");
        put(9231, "中国移动");
        put(58453, "中国移动");

        // 中国电信
        put(4134, "中国电信");
        put(4812, "中国电信");
        put(23724, "中国电信");
        put(136188, "中国电信");
        put(137693, "中国电信");
        put(17638, "中国电信");
        put(140553, "中国电信");
        put(4847, "中国电信");
        put(140061, "中国电信");
        put(136195, "中国电信");
        put(17799, "中国电信");
        put(139018, "中国电信");
        put(133776, "中国电信");
        put(58772, "中国电信");
        put(146966, "中国电信");
        put(63527, "中国电信");
        put(58539, "中国电信");
        put(58540, "中国电信");
        put(141998, "中国电信");
        put(138169, "中国电信");
        put(139203, "中国电信");
        put(58563, "中国电信");
        put(137690, "中国电信");
        put(63838, "中国电信");
        put(137694, "中国电信");
        put(137698, "中国电信");
        put(136167, "中国电信");
        put(148969, "中国电信");
        put(134764, "中国电信");
        put(134770, "中国电信");
        put(148981, "中国电信");
        put(134774, "中国电信");
        put(136190, "中国电信");
        put(140647, "中国电信");
        put(132225, "中国电信");
        put(140485, "中国电信");
        put(4811, "中国电信");
        put(131285, "中国电信");
        put(137689, "中国电信");
        put(137692, "中国电信");
        put(140636, "中国电信");
        put(140638, "中国电信");
        put(140345, "中国电信");
        put(38283, "中国电信");
        put(140292, "中国电信");
        put(140903, "中国电信");
        put(17897, "中国电信");
        put(134762, "中国电信");
        put(139019, "中国电信");
        put(141739, "中国电信");
        put(141771, "中国电信");
        put(134419, "中国电信");
        put(140276, "中国电信");
        put(58542, "中国电信");
        put(140278, "中国电信");
        put(139767, "中国电信");
        put(137688, "中国电信");
        put(137691, "中国电信");
        put(4809, "中国电信");
        put(58466, "中国电信");
        put(137687, "中国电信");
        put(134756, "中国电信");
        put(134760, "中国电信");
        put(133774, "中国电信");
        put(133775, "中国电信");
        put(4816, "中国电信");
        put(134768, "中国电信");
        put(58461, "中国电信");
        put(58519, "中国电信");
        put(58520, "中国电信");
        put(131325, "中国电信");

        // 中国联通
        put(4837, "中国联通");
        put(4808, "中国联通");
        put(134542, "中国联通");
        put(134543, "中国联通");
        put(10099, "中国联通");
        put(140979, "中国联通");
        put(138421, "中国联通");
        put(17621, "中国联通");
        put(17622, "中国联通");
        put(17816, "中国联通");
        put(140726, "中国联通");
        put(17623, "中国联通");
        put(136958, "中国联通");
        put(9929, "中国联通");
        put(58519, "中国联通");
        put(140716, "中国联通");
        put(4847, "中国联通");
        put(136959, "中国联通");
        put(135061, "中国联通");
        put(139007, "中国联通");

        // 云服务商
        put(59019, "金山云");
        put(135377, "优刻云");
        put(45062, "网易云");
        put(137718, "火山引擎");
        put(37963, "阿里云");
        put(45102, "阿里云国际");
        put(45090, "腾讯云");
        put(132203, "腾讯云国际");
        put(55967, "百度云");
        put(38365, "百度云");
        put(58519, "华为云");
        put(55990, "华为云");
        put(136907, "华为云");

        // 其他运营商
        put(4609, "澳門電訊");
        put(134773, "珠江宽频");
        put(1659, "台湾教育网");
        put(8075, "微软云");
        put(17421, "中华电信");
        put(3462, "HiNet");
        put(13335, "Cloudflare");
        put(55960, "亚马逊云");
        put(14618, "亚马逊云");
        put(16509, "亚马逊云");
        put(15169, "谷歌云");
        put(396982, "谷歌云");
        put(36492, "谷歌云");
    }};

    @Autowired
    public GeoIPServiceImpl(Reader cityReader, Reader asnReader, Reader cnReader) {
        this.cityReader = cityReader;
        this.asnReader = asnReader;
        this.cnReader = cnReader;
    }

    @Override
    public IPInfo getIpInfo(String ip) {
        IPInfo info = new IPInfo();
        info.setIp(ip);

        try {
            InetAddress ipAddress = InetAddress.getByName(ip);

            // 处理ASN信息
            processAsnInfo(ipAddress, info);

            // 处理City信息
            Map<String, Object> cityInfo = cityReader.get(ipAddress,Map.class);
            if (cityInfo != null) {
                processCityInfo(cityInfo, info);

                // 如果是中国IP，处理GeoCN数据库
                if (isChinaIp(info)) {
                    processCnInfo(ipAddress, info);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            info.setError(e.getMessage());
        }

        return info;
    }

    @Override
    public IPInfo getIpInfo(HttpServletRequest request) {
        String ip = Optional.ofNullable(request.getHeader("X-Forwarded-For"))
                .map(xff -> xff.split(",")[0])
                .orElse(request.getRemoteAddr());
        return getIpInfo(ip);
    }

    private void processAsnInfo(InetAddress ipAddress, IPInfo info) throws IOException {
        Map<String, Object> asnInfo = asnReader.get(ipAddress,Map.class);
        if (asnInfo == null) return;

        ASInfo as = new ASInfo();
        as.setNumber(Integer.parseInt(asnInfo.get("autonomous_system_number").toString()));
        as.setName(asnInfo.get("autonomous_system_organization").toString());

        String ispName = getAsInfo(as.getNumber());
        if (ispName != null) {
            as.setInfo(ispName);
        }
        info.setAs(as);
    }

    private void processCityInfo(Map<String, Object> cityInfo, IPInfo info) {
        // 处理网络前缀
        Integer prefix = (Integer) cityInfo.get("prefix_len");
        if (prefix == null) prefix = 24; // 默认值
        info.setAddr(getAddr(info.getIp(), prefix));

        // 处理国家信息
        if (cityInfo.get("country") != null) {
            Map<String, Object> country = (Map<String, Object>) cityInfo.get("country");
            CountryInfo countryInfo = new CountryInfo();
            countryInfo.setCode((String) country.get("iso_code"));
            countryInfo.setName(getCountryName(country));
            info.setCountry(countryInfo);
        }

        // 处理注册国家信息
        if (cityInfo.get("registered_country") != null) {
            Map<String, Object> regCountry = (Map<String, Object>) cityInfo.get("registered_country");
            CountryInfo regCountryInfo = new CountryInfo();
            regCountryInfo.setCode((String) regCountry.get("iso_code"));
            regCountryInfo.setName(getCountryName(regCountry));
            info.setRegisteredCountry(regCountryInfo);
        }

        // 处理地区信息
        List<String> regions = new ArrayList<>();
        if (cityInfo.get("subdivisions") != null) {
            List<Map<String, Object>> subdivisions = (List<Map<String, Object>>) cityInfo.get("subdivisions");
            for (Map<String, Object> subd : subdivisions) {
                regions.add(getDescription(subd));
            }
        }

        // 处理城市信息
        if (cityInfo.get("city") != null) {
            Map<String, Object> city = (Map<String, Object>) cityInfo.get("city");
            String cityName = getDescription(city);
            if (regions.isEmpty() || !regions.get(regions.size() - 1).contains(cityName)) {
                regions.add(cityName);
            }
        }

        info.setRegions(deDuplicate(regions));
    }

    private void processCnInfo(InetAddress ipAddress, IPInfo info) throws IOException {
        Map<String, Object> cnData = cnReader.get(ipAddress,Map.class);
        GeoCNResponse cnInfo = new GeoCNResponse(
                (String) cnData.get("province"),
                (String) cnData.get("city"),
                (String) cnData.get("districts"),
                (String) cnData.get("isp"),
                (String) cnData.get("net"),
                (Integer) cnData.getOrDefault("prefix_len", 24)
        );


        if (cnInfo == null) return;

        // 更新地址信息
        info.setAddr(getAddr(info.getIp(), cnInfo.getPrefixLen()));

        // 处理地区信息
        List<String> regions = new ArrayList<>();
        if (cnInfo.getProvince() != null) regions.add(cnInfo.getProvince());
        if (cnInfo.getCity() != null) regions.add(cnInfo.getCity());
        if (cnInfo.getDistricts() != null) regions.add(cnInfo.getDistricts());
        info.setRegions(deDuplicate(regions));

        // 处理简短地区信息
        List<String> regionsShort = new ArrayList<>();
        if (cnInfo.getProvince() != null) regionsShort.add(provinceMatch(cnInfo.getProvince()));
        if (cnInfo.getCity() != null) regionsShort.add(cnInfo.getCity().replace("市", ""));
        if (cnInfo.getDistricts() != null) regionsShort.add(cnInfo.getDistricts());
        info.setRegionsShort(deDuplicate(regionsShort));

        // 更新ISP信息
        if (info.getAs() == null) {
            info.setAs(new ASInfo());
        }
        info.getAs().setInfo(cnInfo.getIsp());

        // 设置网络类型
        if (cnInfo.getNet() != null) {
            info.setType(cnInfo.getNet());
        }
    }

    // 辅助方法 (与Python版本完全一致的功能)
    private String getAsInfo(Integer number) {
        return ASN_MAP.get(number);
    }

    private String getDescription(Map<String, Object> geoObj) {
        if (geoObj.get("names") == null) return "";
        Map<String, String> names = (Map<String, String>) geoObj.get("names");
        for (String l : lang) {
            if (names.containsKey(l)) {
                return names.get(l);
            }
        }
        return names.get("en");
    }

    private String getCountryName(Map<String, Object> country) {
        String name = getDescription(country);
        if (Arrays.asList("香港", "澳门", "台湾").contains(name)) {
            return "中国" + name;
        }
        return name;
    }

    private String provinceMatch(String s) {
        for (String province : provinces) {
            if (s.contains(province)) {
                return province;
            }
        }
        return "";
    }

    private List<String> deDuplicate(List<String> regions) {
        return regions.stream()
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
    }

    private String getAddr(String ip, int prefix) {
        try {
            // 简化实现，实际应使用类似Python的ipaddress计算
            return ip + "/" + prefix;
        } catch (Exception e) {
            return ip + "/" + prefix;
        }
    }

    private boolean isChinaIp(IPInfo info) {
        return info.getCountry() != null &&
                "CN".equals(info.getCountry().getCode()) &&
                (info.getRegisteredCountry() == null ||
                        "CN".equals(info.getRegisteredCountry().getCode()));
    }
}
